package generator;


import java.io.File;
import java.io.FileWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Date;

import org.apache.log4j.Logger;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;

import utility.Utility;
import db.AbstractDbNativeStatements;
import db.Rantanplan;
import db.type.Column;
import db.type.Index;
import db.type.Reference;
import db.type.Table;

/**
 * Persistence Generator
 */
public class Persistence {
	
	private static org.apache.log4j.Logger log = Logger.getLogger(Persistence.class);
	
	private AbstractDbNativeStatements dbStatements = null;
		
	private PersistenceContext context = null;

	
	public Persistence(PersistenceContext context) {
		this.context = context;
	}

	public void start() {

		log.info("Generator started");
		Rantanplan dbInfo = new Rantanplan(this.context);
		
		try {
			
			ArrayList<Table> allTables = dbInfo.getAllTables();
			
			generateDefaultDao();
			generateDefaultDbo();
			generateColumnValueHolder();
			generateAbstractDO();
			generateUtility();
			generateReflectionUtility();
			generatePropertyReader();

			
			for (int i = 0; i < allTables.size(); i++) {
				String tableName = allTables.get(i).getName();
				
				if (this.context.isTableWhitelisted(tableName)
						&& !this.context.isTableBlacklisted(tableName)) {
					ArrayList<Column> columns = dbInfo.getAllColumns(tableName);
					ArrayList<Reference> references = dbInfo.getAllReferences(tableName);
					ArrayList<Index> indexes = dbInfo.getAllIndexes(tableName);
					
					generateGeneratedDO(tableName, columns, references, indexes);
					generateDO(tableName, columns);
					generateGeneratedDao(tableName, columns, references, indexes);
				}
			}
			
			
		} catch (Exception e) {
			log.error("", e);
		} finally {
			try {
				dbInfo.close();
			} catch (Exception e) {
			}
		}
		
		log.info("Generator finished");
	}
	
	private void generateGeneratedDO(String tableName, ArrayList<Column> columns, ArrayList<Reference> references, ArrayList<Index> indexes) {
		log.debug("Generate BusinessObject for table: " + tableName);
		String subfolder = "dbo.generated";
        VelocityEngine ve = new VelocityEngine();
        try {
        	ve.init();
	        Template t = ve.getTemplate(getTemplateString("databaseGeneratedDbo"));
	        VelocityContext context = new VelocityContext();
	        
	        context.put("schema", this.context.getSchema());
	        context.put("table", tableName);
	        String packageName = this.context.getPackageName() + "." + subfolder;
	        context.put("package", packageName);
	        String className = Utility.createJavaNameFromFieldName(true, tableName) + "GeneratedDbo";
	        context.put("classname", className);
	        ArrayList imports = getImportsForDbo(columns, references);
	        imports.remove("imports " + packageName + "." + className +";");
	        context.put("imports", imports);
	        context.put("columns", columns);
	        context.put("references", references);
	        context.put("indexes", indexes);
	        context.put("time", (new Date()).toString());
	        context.put("daoClassname", Utility.createJavaNameFromFieldName(true, tableName) + "GeneratedDao");
	        context.put("daoPackage", this.context.getPackageName() + "." + "dao.generated");
	        
	        writeFile(
	        		t, 
	        		context, 
	        		subfolder.replace(".","/"),
	        		Utility.createJavaNameFromFieldName(true, tableName) + "GeneratedDbo.java");
	
        } catch (Exception e) {
			log.error("", e);
		}
	}
	
	private void generateGeneratedDao(String tableName, ArrayList<Column> columns, ArrayList<Reference> references, ArrayList<Index> indexes) {
		log.debug("Generate Dao for table: " + tableName);
		String subfolder = "dao.generated";
        VelocityEngine ve = new VelocityEngine();
        try {
        	ve.init();
	        Template t = ve.getTemplate(getTemplateString("databaseGeneratedDao"));
	        VelocityContext context = new VelocityContext();
	        
	        context.put("schema", this.context.getSchema());
	        context.put("table", tableName);
	        context.put("package", this.context.getPackageName() + "." + subfolder);
	        context.put("classname", Utility.createJavaNameFromFieldName(true, tableName) + "GeneratedDao");
	        String dboClassname = Utility.createJavaNameFromFieldName(true, tableName) + "Dbo";
	        context.put("dboClassname", dboClassname);
	        String dboPackage = this.context.getPackageName() + ".dbo";
	        context.put("dboPackage", dboPackage);
	        ArrayList imports = getImportsForDao(columns, references, indexes);
	        String defaultDaoName = this.context.getDbAlias().substring(0,1).toUpperCase() + this.context.getDbAlias().substring(1, this.context.getDbAlias().length()) + "DefaultDao";
	        imports.add("import " + this.context.getPackageName() +"." + defaultDaoName + ";");
	        String dboImport = "import " + dboPackage + "." + dboClassname + ";";
	        if (!imports.contains(dboImport)) {
	        	imports.add(dboImport);
	        }
	        context.put("imports", imports);
	        context.put("columns", columns);
	        context.put("references", references);
	        context.put("indexes", indexes);
	        context.put("time", (new Date()).toString());
	        
	        context.put("time", (new Date()).toString());
	        context.put("defaultDaoClassName", defaultDaoName);
	        writeFile(
	        		t, 
	        		context, 
	        		subfolder.replace(".","/"),
	        		Utility.createJavaNameFromFieldName(true, tableName) + "GeneratedDao.java");
	
        } catch (Exception e) {
			log.error("", e);
		}
		
		
	}
	
	private void generateDO(String tableName, ArrayList<Column> columns) {
		log.debug("Generate DatabaseObject for table: " + tableName);
		
        VelocityEngine ve = new VelocityEngine();
        try {
        	ve.init();
	        Template t = ve.getTemplate(getTemplateString("databaseObject"));
	        VelocityContext context = new VelocityContext();
	        
	        context.put("schema", this.context.getSchema());
	        context.put("table", tableName);
	        context.put("package", this.context.getPackageName() + ".dbo");
	        context.put("classname", Utility.createJavaNameFromFieldName(true, tableName) + "Dbo");
	        ArrayList<String> imports = new ArrayList<String>();
	        imports.add("import " + this.context.getPackageName() + ".dbo.generated." + Utility.createJavaNameFromFieldName(true, tableName) + "GeneratedDbo;");
	        context.put("imports", imports);
	        context.put("columns", columns);
	        context.put("time", (new Date()).toString());
	        context.put("generatedClass",  Utility.createJavaNameFromFieldName(true, tableName) + "GeneratedDbo");
	        
	        
	        writeFile(t, context, "dbo", Utility.createJavaNameFromFieldName(true, tableName) + "Dbo.java");
        } catch (Exception e) {
        	log.error("", e);
		}
		
		
	}
	
	private void generateAbstractDO() {
		log.debug("Generate AbstractDatabaseObject");
		
        VelocityEngine ve = new VelocityEngine();
        try {
			ve.init();
			
	        Template t = ve.getTemplate(getTemplateString("databaseAbstractDatabaseObject"));
	        VelocityContext context = new VelocityContext();
	        
	        context.put("package", this.context.getPackageName());
	        context.put("time", (new Date()).toString());
	        
	        writeFile(t, context, null, "AbstractDatabaseObject.java");
	
        } catch (Exception e) {
        	log.error("", e);
		}		
	}
	
	private void generateDefaultDao() {
		log.debug("Generate DefaultDao");
		
        VelocityEngine ve = new VelocityEngine();
        try {
			ve.init();
	        Template t = ve.getTemplate(getTemplateString("databaseDefaultDao"));
	        VelocityContext context = new VelocityContext();
	        
	        context.put("package", this.context.getPackageName());
	        context.put("time", (new Date()).toString());
	        context.put("dbAlias", this.context.getDbAlias());
	        String daoName = this.context.getDbAlias().substring(0,1).toUpperCase() + this.context.getDbAlias().substring(1, this.context.getDbAlias().length()) + "DefaultDao";
	        context.put("defaultDaoClassName", daoName);
	        
	        writeFile(t, context, null, daoName + ".java");
	
        } catch (Exception e) {
        	log.error("", e);
		}		
	}
	
	private void generateDefaultDbo() {
		log.debug("Generate DefaultDbo");
		
        VelocityEngine ve = new VelocityEngine();
        try {
			ve.init();
	        Template t = ve.getTemplate(getTemplateString("databaseDefaultDbo"));
	        VelocityContext context = new VelocityContext();
	        
	        context.put("package", this.context.getPackageName());
	        context.put("time", (new Date()).toString());
	        
	        writeFile(t, context, null, "DefaultDbo.java");
	
        } catch (Exception e) {
        	log.error("", e);
		}		
	}
	
	private void generateColumnValueHolder() {
		log.debug("Generate ColumnValueHolder");
		
        VelocityEngine ve = new VelocityEngine();
        try {
			ve.init();
	        Template t = ve.getTemplate(getTemplateString("databaseColumnValueHolder"));
	        VelocityContext context = new VelocityContext();
	        
	        context.put("package", this.context.getPackageName());
	        context.put("time", (new Date()).toString());
	        
	        writeFile(t, context, null, "ColumnValueHolder.java");
	
        } catch (Exception e) {
        	log.error("", e);
		}		
	}
	
	private void generatePropertyReader() {
		log.debug("Generate PropertyReader");
		String subfolder = "utility";
        VelocityEngine ve = new VelocityEngine();
        try {
			ve.init();
	        Template t = ve.getTemplate(getTemplateString("propertyReader"));
	        VelocityContext context = new VelocityContext();
	        
	        context.put("package", this.context.getPackageName() + "." + subfolder);
	        context.put("time", (new Date()).toString());
	        
	        writeFile(t, context, subfolder, "PropertyReader.java");
	
        } catch (Exception e) {
        	log.error("", e);
		}		
	}
	
	private void generateReflectionUtility() {
		log.debug("Generate ReflectionUtility");
		String subfolder = "utility";
        VelocityEngine ve = new VelocityEngine();
        try {
			ve.init();
	        Template t = ve.getTemplate(getTemplateString("reflectionUtility"));
	        VelocityContext context = new VelocityContext();
	        
	        context.put("package", this.context.getPackageName() + "." + subfolder);
	        context.put("time", (new Date()).toString());
	        
	        writeFile(t, context, subfolder, "ReflectionUtility.java");
	
        } catch (Exception e) {
        	log.error("", e);
		}		
	}
	
	private void generateUtility() {
		log.debug("Generate AbstractDatabaseObject");
		String subfolder = "utility";
        VelocityEngine ve = new VelocityEngine();
        try {
			ve.init();
	        Template t = ve.getTemplate(getTemplateString("utility"));
	        VelocityContext context = new VelocityContext();
	        
	        context.put("package", this.context.getPackageName() + "." + subfolder);
	        context.put("time", (new Date()).toString());
	        
	        writeFile(t, context, subfolder, "Utility.java");
	
        } catch (Exception e) {
        	log.error("", e);
		}		
	}
	
	private String getTemplateString(String templateName) {
		return "classes" + "/" + this.getClass().getPackage().getName().replace(".","/") + "/template/" + templateName + ".vm";
	}
	
	private void writeFile(Template t, VelocityContext context, String subfolder, String filename) {
		 try {
			StringWriter writer = new StringWriter();
			t.merge(context, writer);

			if (subfolder == null) {
				subfolder = "";
			}
			String folderString = this.context.getSourceFolder() + this.context.getPackageFolder() + subfolder + "/";
			File folder = new File(folderString);
			if (!folder.exists()) {
				folder.mkdirs();
			}

			File file = new File(folderString + filename);

			FileWriter fw = new FileWriter(file);
			fw.write(writer.toString());
			fw.flush();
			fw.close();
		} catch (Exception e) {
			log.error("", e);
		}	
	}
		
	private ArrayList<String> getImportsForDao(ArrayList columns, ArrayList references, ArrayList indexes) {
		ArrayList<String> result = new ArrayList<String>();
		
		result.add("import java.util.ArrayList;");
		result.add("import java.sql.SQLException;");
		
		for (int i = 0; i < columns.size(); i++) {
			if (((Column) columns.get(i)).isPk()) {
				String importTimestamp = "import java.sql.Timestamp;";
				if (((Column) columns.get(i)).getJavaTyp().equals("Timestamp")
						&& !result.contains(importTimestamp)) {
					result.add(importTimestamp);
				}
				String importDate = "import java.sql.Date;";
				if (((Column) columns.get(i)).getJavaTyp().equals("Date")
						&& !result.contains(importDate)) {
					result.add(importDate);
				}
				String importBlob = "import oracle.sql.BLOB;";
				if (((Column) columns.get(i)).getJavaTyp().equals("BLOB")
						&& !result.contains(importDate)) {
					result.add(importBlob);
				}
			}
		}
		
		for (int i = 0; i < references.size(); i++) {
			Reference reference = (Reference) references.get(i);
			String imp = "import " + this.context.getPackageName() + ".dbo." + reference.getSourceColumn().getTable().getJavaName() + ";";
			if (!result.contains(imp)) {
				result.add(imp);
			}
			imp = "import " + this.context.getPackageName() + ".dbo." + reference.getTargetColumn().getTable().getJavaName() + ";";
			if (!result.contains(imp)) {
				result.add(imp);
			}
		}
		
		for (int i = 0; i < indexes.size(); i++) {
			Index index = (Index) indexes.get(i);
			for (int j = 0; j < index.getColumns().size(); j++) {
				String importTimestamp = "import java.sql.Timestamp;";
				Column col = index.getColumns().get(j);
				if (col.getJavaTyp().equals("Timestamp")
						&& !result.contains(importTimestamp)) {
					result.add(importTimestamp);
				}
				String importDate = "import java.sql.Date;";
				if (col.getJavaTyp().equals("Date")
						&& !result.contains(importDate)) {
					result.add(importDate);
				}
			}
			
		}
		
		return result;
	}
	
	private ArrayList<String> getImportsForDbo(ArrayList columns, ArrayList references) {
		ArrayList<String> result = new ArrayList<String>();
		
		result.add("import java.util.ArrayList;");
		
		result.add("import " + this.context.getPackageName() +".AbstractDatabaseObject;");
		result.add("import org.apache.log4j.Logger;");
		
		for (int i = 0; i < columns.size(); i++) {
			String importTimestamp = "import java.sql.Timestamp;";
			if (((Column) columns.get(i)).getJavaTyp().equals("Timestamp")
					&& !result.contains(importTimestamp)) {
				result.add(importTimestamp);
			}
			String importDate = "import java.sql.Date;";
			if (((Column) columns.get(i)).getJavaTyp().equals("Date")
					&& !result.contains(importDate)) {
				result.add(importDate);
			}
			String importBlob = "import oracle.sql.BLOB;";
			if (((Column) columns.get(i)).getJavaTyp().equals("BLOB")
					&& !result.contains(importDate)) {
				result.add(importBlob);
			}
		}
		
		for (int i = 0; i < references.size(); i++) {
			Reference reference = (Reference) references.get(i);
			String imp = "import " + this.context.getPackageName() + ".dbo." + reference.getSourceColumn().getTable().getJavaName() + ";";
			if (!result.contains(imp)) {
				result.add(imp);
			}
			imp = "import " + this.context.getPackageName() + ".dbo." + reference.getTargetColumn().getTable().getJavaName() + ";";
			if (!result.contains(imp)) {
				result.add(imp);
			}
		}
		
		return result;
	}
}
